#import <Foundation/Foundation.h>
#import <QTKit/QTKit.h>
#import <Quicktime/Movies.h>


int toSeconds(QTTime);
void checkError(NSError* error);
void buildTrack(NSArray*, NSString*, NSArray*, int); 
void addMovieClip(QTMovie* final, QTMovie* movie, int start, int duration);
void monitorLoad(QTMovie* movie);
void dumpMovieMetadata(QTMovie* movie);
void addMovieMetaData(QTMovie* aQTMovie, OSType key, NSString* aNameStr);
NSArray* getSongsInPlayList(NSDictionary* library, NSString* name);
NSArray* availableComponents();
NSData* getExportSettings();
void reportOverlap(NSArray* left, NSArray* right);

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
    srandom(time(NULL));
	
	NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
    NSString* desktop = [paths objectAtIndex:0];
	
	NSError* error;
	NSString* output = [desktop stringByAppendingString:@"/final-cut.m4a"];
	
	NSLog(@"[%@]\n", output);
	
	if([[NSFileManager defaultManager] isReadableFileAtPath:output]){
		[[NSFileManager defaultManager] removeItemAtPath:output error:&error]; 
	}
	
	NSLog(@"Interrogating iTunes\n");
	
	NSDictionary* library = [NSDictionary dictionaryWithContentsOfFile:
							 @"/Users/ufotofu/Music/iTunes/iTunes Music Library.xml"];
	
	
	
	NSArray* fast_songs = getSongsInPlayList(library, @"Fast Songs");
	if( !fast_songs ){
		NSLog(@"Couldn't find playlist: %@\n", @"Fast Songs");
		return 1;
	}
	
	NSArray* slow_songs = getSongsInPlayList(library, @"Slow Songs");
	if( !slow_songs ){
		NSLog(@"Couldn't find playlist: %@\n", @"Slow Songs");
		return 2;
	}
	
	reportOverlap(slow_songs, fast_songs);
	
	
	int walk = 5;
	int run = 3;
	int total = 60;
	
	NSArray* songs = [NSArray arrayWithObjects:slow_songs, fast_songs,nil];
	NSArray* pattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:60*walk], [NSNumber numberWithInt:60*run], nil];
	
    buildTrack(songs, output, pattern, 60*total);
	
	NSLog(@"*****\n*****\n*****\n*****\n*****\n");
	
	
	NSDictionary* attributes= [NSDictionary dictionaryWithObjectsAndKeys:
							   [NSNumber numberWithBool:YES], QTMovieEditableAttribute,
							   output, QTMovieFileNameAttribute,
							   [NSNumber numberWithBool:NO], QTMovieOpenAsyncOKAttribute,
							   nil];
	
	
	QTMovie* check = [[QTMovie alloc] initWithAttributes:attributes error:&error];
	if(error){
		checkError(error);
		return 3;
	}
	monitorLoad(check);
	
	NSDateFormatter* format = [[NSDateFormatter alloc] init];
	[format setDateFormat:@"yyyyMMddHHmmss"];
	NSString* now = [format stringFromDate:[NSDate date]];
	
	NSString* display_name = [NSString stringWithFormat:@"%d-%d-%d_%@", walk, run, total, now]; 
	
	addMovieMetaData(check, kQTMetaDataCommonKeyArtist, @"Andrew Pressel");
	addMovieMetaData(check, kQTMetaDataCommonKeyAlbum, @"Run Fat Boy, Run");
	addMovieMetaData(check, kQTMetaDataCommonKeyDisplayName, display_name);		
	
	//dumpMovieMetadata(check);
	
	NSDictionary* attr2 = [NSDictionary dictionaryWithObjectsAndKeys:
						   [NSNumber numberWithBool:YES], QTMovieFlatten, nil];
	
	[check writeToFile:@"/Users/ufotofu/Desktop/output.m4a" withAttributes:attr2 error:&error];
	checkError(error);
	
	[pool drain];
    return 0;
}



NSUInteger random_below(NSUInteger n) {
    NSUInteger m = 1;
	
    do {
        m <<= 1;
    } while(m < n);
	
    NSUInteger ret;
	
    do {
        ret = random() % m;
    } while(ret >= n);
	
    return ret;
}

void shuffle(NSMutableArray* list) {
    for(NSUInteger i = [list count]; i > 1; i--) {
        NSUInteger j = random_below(i);
        [list exchangeObjectAtIndex:i-1 withObjectAtIndex:j];
    }
}



NSArray* getSongsInPlayList(NSDictionary* library, NSString* name){
	NSMutableArray* retval = [NSMutableArray array];
	
	NSArray* playlists= [library objectForKey:@"Playlists"];
	NSDictionary* tracks = [library objectForKey:@"Tracks"];
	
	NSDictionary* wanted = nil;
	for(int i = 0; i<playlists.count; i++){
		if([[[playlists objectAtIndex:i] objectForKey:@"Name"] isEqualTo:name]){
			wanted = [playlists objectAtIndex:i];
		}
	}
	
	if(!wanted){
		return nil;
	}
	
	for(id item in [wanted objectForKey:@"Playlist Items"]){
		NSNumber* trk_id = [item objectForKey:@"Track ID"];
		NSDictionary* trk = [tracks objectForKey:[trk_id stringValue]];
		NSURL* url = [NSURL URLWithString:[trk objectForKey:@"Location"]];
		
		if( ![trk objectForKey:@"Protected"]  ){
			[retval addObject:url];
		}else{
			NSLog(@"Protected in %@:[\n  Album  => %@,\n  Artist => %@\n  Name   => %@\n]\n",
				  name,
				  [trk objectForKey:@"Album"],
				  [trk objectForKey:@"Artist"],
				  [trk objectForKey:@"Name"]);
		}
	}
	
	shuffle(retval);
	shuffle(retval);
	shuffle(retval);
	
	return retval;
}


void monitorLoad(QTMovie* movie){
	NSInteger loadState = [[movie attributeForKey:QTMovieLoadStateAttribute] longValue];
	while(loadState != QTMovieLoadStateComplete){
		switch (loadState) {
			case QTMovieLoadStateError:
		        NSLog(@"an error occurred while loading the movie\n");
				break;
			case QTMovieLoadStateLoading:
				NSLog(@"the movie is loading\n");
				break;
			case QTMovieLoadStateLoaded:
				NSLog(@"the movie atom has loaded; it's safe to query movie properties\n");
				break;
			case QTMovieLoadStatePlayable:
				NSLog(@"the movie has loaded enough media data to begin playing\n");
				break;
			case QTMovieLoadStatePlaythroughOK:
				NSLog(@"the movie has loaded enough media data to play through to end\n");
				break;
			case QTMovieLoadStateComplete:
				NSLog(@"the movie has loaded completely\n");
				break;
			default:
				NSLog(@"don't know what to do here\n");
		}
		
		loadState = [[movie attributeForKey:QTMovieLoadStateAttribute] longValue];
	}
	
}

// Return the default language set by the user 
// in the language tab of the International preference
// pane.
const char* langCode()
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
 	
    NSArray *languages = [defaults objectForKey:@"AppleLanguages"];
	
    NSString *langStr = [languages objectAtIndex:0];
	
    return ([langStr cStringUsingEncoding:NSMacOSRomanStringEncoding]);
}


// Add the artist name metadata item to a movie file
void addMovieMetaData(QTMovie* aQTMovie, OSType key, NSString* aNameStr){
    QTMetaDataRef   metaDataRef;
    Movie           theMovie;
    OSStatus        status;
	
    theMovie = [aQTMovie quickTimeMovie];
    status = QTCopyMovieMetaData (theMovie, &metaDataRef );
	if(status == noErr){
		NSLog(@"Able to get metadata\n");
	}
	
	
    if (status == noErr){
        const char *nameCStringPtr = [aNameStr UTF8String];
		NSLog(@"[%d] => [%s]\n", key, nameCStringPtr);
		
        if (nameCStringPtr){
            QTMetaDataItem outItem;
            status = QTMetaDataAddItem(metaDataRef,
									   kQTMetaDataStorageFormatiTunes, 
									   kQTMetaDataKeyFormatCommon,
									   (const UInt8 *)&key,
									   sizeof(key),
									   (const UInt8 *)nameCStringPtr,
									   strlen(nameCStringPtr),
									   kQTMetaDataTypeUTF8,
									   &outItem);
			
			if(status == noErr){
				NSLog(@"No err QTMetaDataAddItem\n");
			}
			
            // it is also recommended you set the locale identifier
            const char *langCodeStr = langCode();
			NSLog(@"lang code => [%s]\n", langCodeStr);
			
            status = QTMetaDataSetItemProperty(
											   metaDataRef,
											   outItem,
											   kPropertyClass_MetaDataItem,
											   kQTMetaDataItemPropertyID_Locale,
											   strlen(langCodeStr) + 1,
											   langCodeStr);
			if(status == noErr){
				NSLog(@"NO ERR QTMetaDataSetItemProperty\n");
			}
			
        }
		
        QTMetaDataRelease(metaDataRef);
    }
	
	
}

NSArray* getTransitions(){
	
	NSError* error;
   	NSMutableDictionary* attributes= [NSMutableDictionary dictionary];
	NSMutableArray* retval = [NSMutableArray arrayWithCapacity:5];
	
	[attributes setObject:@"/Users/ufotofu/Developer/Projects/aac-restitch/sounds/06496.m4a" forKey:QTMovieFileNameAttribute];
	QTMovie* mov = [[QTMovie alloc] initWithAttributes:attributes error:&error];
	if( error ){
		NSLog(@"Error opening output file: [%@]\n",error);
		return nil;
	}	
	[retval addObject:mov];
	
	[attributes setObject:@"/Users/ufotofu/Developer/Projects/aac-restitch/sounds/06499.m4a" forKey:QTMovieFileNameAttribute];
	mov = [[QTMovie alloc] initWithAttributes:attributes error:&error];
	if( error ){
		NSLog(@"Error opening output file: [%@]\n", error);
		return nil;
	}	
	[retval addObject:mov];
	
	[attributes setObject:@"/Users/ufotofu/Developer/Projects/aac-restitch/sounds/06504.m4a" forKey:QTMovieFileNameAttribute];
	mov = [[QTMovie alloc] initWithAttributes:attributes error:&error];
	if( error ){
		NSLog(@"Error opening output file: [%@]\n", error);
		return nil;
	}	
	[retval addObject:mov];
	
	[attributes setObject:@"/Users/ufotofu/Developer/Projects/aac-restitch/sounds/AOS00842.m4a" forKey:QTMovieFileNameAttribute];
	mov = [[QTMovie alloc] initWithAttributes:attributes error:&error];
	if( error ){
		NSLog(@"Error opening output file: [%@]\n", error);
		return nil;
	}	
	[retval addObject:mov];
	
	[attributes setObject:@"/Users/ufotofu/Developer/Projects/aac-restitch/sounds/AOS00855.m4a" forKey:QTMovieFileNameAttribute];
	mov = [[QTMovie alloc] initWithAttributes:attributes error:&error];
	if( error ){
		NSLog(@"Error opening output file: [%@]\n", error);
		return nil;
	}	
	[retval addObject:mov];	
	
	return retval;
}


QTMovie* getFinalMixMovie(NSString* output){
	
	NSDictionary* attributes= [NSDictionary dictionaryWithObjectsAndKeys:
							   [NSNumber numberWithBool:NO], QTMovieOpenForPlaybackAttribute,
							   [NSNumber numberWithBool:YES], QTMovieEditableAttribute,
							   nil];
	
	
	
	NSError* error;
	QTMovie* final = [[QTMovie alloc] initWithAttributes:attributes error:&error];
	if( error ){
		NSLog(@"%@\nError opening output file: [%@]\n", output,error);
		return nil;
	}	
	
	[final setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute];
	
	return final;
}

void buildTrack(NSArray* songs, NSString* output, NSArray* pattern, int max_time){
	
	NSError* error;
	
	NSArray* xfade = getTransitions();
	
	QTMovie* final = getFinalMixMovie(output);
	if( !final ){
		NSLog(@"Error Opening Final File\n");
		return;
	}
	
	int offset[2] = {0,0};
	
	NSMutableArray* movies = [NSMutableArray array];
	for(int i=0; i<pattern.count; i++){
		NSMutableArray* list = [songs objectAtIndex:i];
		NSURL* file = [list objectAtIndex:0];
		[list removeObjectAtIndex:0];
		
		QTMovie* mv = [QTMovie movieWithURL:file error:&error];
		monitorLoad(mv);
		
		NSLog(@"%d: Loading: %@", i, [mv attributeForKey:QTMovieDisplayNameAttribute]);
		
		int sections = toSeconds(mv.duration) / [[pattern objectAtIndex:i] intValue];
		printf("sections: %d\n", sections);
		
		[movies addObject:[QTMovie movieWithURL:file error:&error]];
		
		//NSLog(@"%@\n", [[movies objectAtIndex:i] movieAttributes]);
	}
	
	int size = 0;
	int iter = 0;
	int stage = 0;
	
	while(size <= max_time){
		int step = [[pattern objectAtIndex:iter] intValue];
		
		QTMovie* mov = [movies objectAtIndex:iter];
		monitorLoad(mov);
		
		printf("iter:%d  step:%d offset:%d duration:%d stage:%d\n", iter, step, offset[iter], toSeconds(mov.duration), stage);
		
		
		int remains = step - stage;
		
		int piece = toSeconds(mov.duration) - offset[iter];
		int length = remains;
		if( piece < remains ){
			length = piece;
		}
		
		addMovieClip(final, mov, offset[iter], length);
		
		size += length;
		offset[iter]+= length;
		stage += length;
		
		if( offset[iter] >= toSeconds(mov.duration) ){
			NSMutableArray* list = [songs objectAtIndex:iter];
			NSURL* file = [list objectAtIndex:0];
			[list removeObjectAtIndex:0];
			
			QTMovie* mv = [QTMovie movieWithURL:file error:&error];
			monitorLoad(mv);
			
			NSLog(@"%d ran out using: %@", iter, [mv attributeForKey:QTMovieDisplayNameAttribute]);
			
			[movies replaceObjectAtIndex:iter withObject:mv];
			offset[iter] = 0;
		}
		
		if( stage >= step ){
			iter = (iter + 1) % pattern.count; 
			mov = [xfade objectAtIndex:random_below(5)];
			printf("transition:%d\n", toSeconds(mov.duration));
			addMovieClip(final, mov, 0, toSeconds(mov.duration));
			stage = 0;
		}
		
		
	}
	
	printf("Completed: %d\n", size);
	
	NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys:
								[NSNumber numberWithBool:YES], QTMovieExport,
								[NSNumber numberWithLong:kQTFileTypeMP4], QTMovieExportType,
								//[NSNumber numberWithLong:kAppleManufacturer], QTMovieExportManufacturer,
								nil];
	
	NSLog(@"Exporting Wave File: %@\n", output);
	[final writeToFile:output withAttributes:attributes error:&error];	
	checkError(error);
}

void dumpMovieMetadata(QTMovie* movie){
	Movie qtm = [movie quickTimeMovie];
	QTMetaDataRef metadata;
	OSStatus status = QTCopyMovieMetaData (qtm, &metadata );
	
	if(status != noErr ){
		NSLog(@"Error retrieving metadata\n");
		return;
	}
	
	int i = 0;
	QTMetaDataItem item = kQTMetaDataItemUninitialized;
	while (noErr == QTMetaDataGetNextItem (metadata,
										   kQTMetaDataStorageFormatiTunes,
										   item,
										   kQTMetaDataKeyFormatCommon,
										   nil,
										   0,
										   &item)) {
		
		// do stuff with item
		UInt8 buffy[100];
		// ...
		QTPropertyValuePtr key = &buffy;
		ByteCount keySize = 0;
		QTMetaDataGetItemProperty (metadata,
								   item,
								   kPropertyClass_MetaDataItem,
								   kQTMetaDataItemPropertyID_Key, 
								   sizeof (buffy),
								   key,
								   &keySize);
		NSString* keyString =
		[NSString stringWithCString: key length: keySize];
		
		// get value size
		ByteCount valueSize = 0;
		QTMetaDataGetItemValue (metadata,
								item,
								NULL,
								0, 
								&valueSize);
		// get value
		char valueBuf[valueSize];
		QTMetaDataGetItemValue (metadata,
								item,
								&valueBuf,
								valueSize, 
								nil);
		NSLog(@"Item [%d]: [%@] => [%s]\n", i, keyString, valueBuf);
		i++;
	}	
}

void addMovieClip(QTMovie* final, QTMovie* movie, int start, int duration){
	NSNumber* scale = [movie attributeForKey:QTMovieTimeScaleAttribute];
	QTTime time = QTMakeTime(start * [scale longValue], [scale longValue]);
	QTTime dura = QTMakeTime(duration * [scale longValue], [scale longValue]);
	QTTimeRange rng = QTMakeTimeRange(time, dura);
	
	[movie setSelection:rng];
	[final appendSelectionFromMovie:movie];
}

void reportOverlap(NSArray* left, NSArray* right){
	for( id url in left ){
		
		if( [right containsObject:url] ){
			NSLog(@"Overlapping Tracks:\n%@\n", [url lastPathComponent]);
		}
	}
}

void checkError(NSError* error){
	if( error ){
		NSLog(@"Error occurred: [%@]\n", error);
	}
}

int toSeconds(QTTime time){
	return time.timeValue/time.timeScale;
}
